import os
import sys

import pytest

from hatch.config.constants import AppEnvVars, ConfigEnvVars
from hatch.project.core import Project
from hatch.python.core import PythonManager
from hatch.python.resolve import get_compatible_distributions
from hatch.utils.fs import Path
from hatch.utils.structures import EnvVars
from hatchling.utils.constants import DEFAULT_BUILD_SCRIPT, DEFAULT_CONFIG_FILE


@pytest.fixture(scope="module")
def available_python_version():
    compatible_distributions = get_compatible_distributions()
    current_version = f"{sys.version_info.major}.{sys.version_info.minor}"
    if current_version in compatible_distributions:
        return current_version

    versions = [d for d in get_compatible_distributions() if not d.startswith("pypy")]
    return versions[-1]


def test_help(hatch):
    short = hatch("run", "-h")
    assert short.exit_code == 0, short.output

    long = hatch("run", "--help")
    assert long.exit_code == 0, long.output

    assert short.output == long.output


def test_automatic_creation(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("run", "python", "-c", "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Creating environment: default
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    assert str(env_path) in str(output_file.read_text())


def test_no_compatibility_check_if_exists(hatch, helpers, temp_dir, config_file, mocker):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("run", "python", "-c", "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Creating environment: default
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    assert str(env_path) in str(output_file.read_text())

    output_file.unlink()
    mocker.patch("hatch.env.virtual.VirtualEnvironment.check_compatibility", side_effect=Exception("incompatible"))
    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("run", "python", "-c", "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)")

    assert result.exit_code == 0, result.output
    assert not result.output
    assert str(env_path) in str(output_file.read_text())


def test_enter_project_directory(hatch, config_file, helpers, temp_dir):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = "foo"
    config_file.model.mode = "project"
    config_file.model.project = project
    config_file.model.projects = {project: str(project_path)}
    config_file.save()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})

    with EnvVars({ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("run", "python", "-c", "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Creating environment: default
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    assert str(env_path) in str(output_file.read_text())


@pytest.mark.requires_internet
def test_sync_dependencies(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("env", "create", "default")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Creating environment: default
        Checking dependencies
        """
    )

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    project = Project(project_path)
    helpers.update_project_environment(
        project, "default", {"dependencies": ["binary"], **project.config.envs["default"]}
    )

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "python",
            "-c",
            "import binary,pathlib,sys;pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))",
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Checking dependencies
        Syncing dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    assert str(output_file.read_text()) == "(1.0, 'KiB')"


@pytest.mark.requires_internet
def test_sync_project_dependencies(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("env", "create", "default")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Creating environment: default
        Installing project in development mode
        Checking dependencies
        """
    )

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    project = Project(project_path)
    config = dict(project.raw_config)
    config["project"]["dependencies"] = ["binary"]
    project.save_config(config)

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "python",
            "-c",
            "import binary,pathlib,sys;pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))",
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Checking dependencies
        Syncing dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    assert str(output_file.read_text()) == "(1.0, 'KiB')"


@pytest.mark.requires_internet
def test_sync_project_features(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    config = dict(project.raw_config)
    config["project"]["optional-dependencies"] = {"foo": []}
    project.save_config(config)
    helpers.update_project_environment(project, "default", {"features": ["foo"], **project.config.envs["default"]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("env", "create", "default")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Creating environment: default
        Installing project in development mode
        Checking dependencies
        """
    )

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    project = Project(project_path)
    config = dict(project.raw_config)
    config["project"]["optional-dependencies"]["foo"].append("binary")
    project.save_config(config)

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "python",
            "-c",
            "import binary,pathlib,sys;pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))",
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Checking dependencies
        Syncing dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    assert str(output_file.read_text()) == "(1.0, 'KiB')"


@pytest.mark.requires_internet
def test_dependency_hash_checking(hatch, helpers, temp_dir, config_file, mocker):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("env", "create", "default")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Creating environment: default
        Checking dependencies
        """
    )

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    project = Project(project_path)
    helpers.update_project_environment(
        project, "default", {"dependencies": ["binary"], **project.config.envs["default"]}
    )

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "python",
            "-c",
            "import binary,pathlib,sys;pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))",
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Checking dependencies
        Syncing dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    assert str(output_file.read_text()) == "(1.0, 'KiB')"
    output_file.unlink()

    # Now there should be no output because there is no dependency checking
    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "python",
            "-c",
            "import binary,pathlib,sys;pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))",
        )

    assert result.exit_code == 0, result.output
    assert not result.output

    output_file = project_path / "test.txt"
    assert output_file.is_file()

    assert str(output_file.read_text()) == "(1.0, 'KiB')"
    output_file.unlink()

    mocker.patch("hatch.env.virtual.VirtualEnvironment.dependencies_in_sync", return_value=False)
    mocker.patch("hatch.env.virtual.VirtualEnvironment.dependency_hash", side_effect=["foo", "bar", "bar"])

    # Ensure that the saved value is the second value
    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "python",
            "-c",
            "import binary,pathlib,sys;pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))",
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Checking dependencies
        Syncing dependencies
        """
    )

    output_file = project_path / "test.txt"
    assert output_file.is_file()

    assert str(output_file.read_text()) == "(1.0, 'KiB')"
    output_file.unlink()

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "python",
            "-c",
            "import binary,pathlib,sys;pathlib.Path('test.txt').write_text(str(binary.convert_units(1024)))",
        )

    assert result.exit_code == 0, result.output
    assert not result.output

    output_file = project_path / "test.txt"
    assert output_file.is_file()

    assert str(output_file.read_text()) == "(1.0, 'KiB')"


def test_scripts(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(
        project,
        "default",
        {"skip-install": True, "scripts": {"py": "python -c {args}"}, **project.config.envs["default"]},
    )

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("run", "py", "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Creating environment: default
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    assert str(env_path) in str(output_file.read_text())


def test_scripts_specific_environment(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(
        project,
        "default",
        {"skip-install": True, "scripts": {"py": "python -c {args}"}, **project.config.envs["default"]},
    )
    helpers.update_project_environment(project, "test", {"env-vars": {"foo": "bar"}})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "test:py",
            "import os,pathlib,sys;pathlib.Path('test.txt').write_text("
            "sys.executable+os.linesep[-1]+os.environ['foo'])",
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Creating environment: test
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == "test"

    python_executable_path, env_var_value = str(output_file.read_text()).splitlines()
    assert str(env_path) in python_executable_path
    assert env_var_value == "bar"


@pytest.mark.requires_internet
def test_scripts_no_environment(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    config = dict(project.raw_config)
    config["tool"]["hatch"]["scripts"] = {"py": "python -c {args}"}
    project.save_config(config)

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("run", ":py", "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Checking dependencies
        """
    )

    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert not env_data_path.exists()

    assert os.path.realpath(output_file.read_text().strip()).lower() == os.path.realpath(sys.executable).lower()


def test_error(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(
        project,
        "default",
        {
            "skip-install": True,
            "scripts": {
                "error": [
                    'python -c "import sys;sys.exit(3)"',
                    "python -c \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\"",
                ],
            },
            **project.config.envs["default"],
        },
    )

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("run", "error")

    assert result.exit_code == 3
    assert result.output == helpers.dedent(
        """
        Creating environment: default
        Checking dependencies
        cmd [1] | python -c "import sys;sys.exit(3)"
        """
    )
    output_file = project_path / "test.txt"
    assert not output_file.is_file()


def test_ignore_error(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(
        project,
        "default",
        {
            "skip-install": True,
            "scripts": {
                "error": [
                    '- python -c "import sys;sys.exit(3)"',
                    "python -c \"import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)\"",
                ],
            },
            **project.config.envs["default"],
        },
    )

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("run", "error")

    assert result.exit_code == 0
    assert result.output == helpers.dedent(
        """
        Creating environment: default
        Checking dependencies
        cmd [1] | - python -c "import sys;sys.exit(3)"
        cmd [2] | python -c "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)"
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    assert str(env_path) in str(output_file.read_text())


def test_command_expansion_error(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(
        project,
        "default",
        {"skip-install": True, "scripts": {"error": ["echo {env:FOOBAR}"]}, **project.config.envs["default"]},
    )

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("run", "error")

    assert result.exit_code == 1
    assert result.output == helpers.dedent(
        """
        Creating environment: default
        Checking dependencies
        Nonexistent environment variable must set a default: FOOBAR
        """
    )
    output_file = project_path / "test.txt"
    assert not output_file.is_file()


def test_verbosity(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(
        project,
        "default",
        {
            "skip-install": True,
            "scripts": {
                "write-exe": [
                    "python -c \"import pathlib,sys;pathlib.Path('{args}1.txt').write_text(sys.executable)\"",
                    "python -c \"import pathlib,sys;pathlib.Path('{args}2.txt').write_text(sys.executable)\"",
                    "python -c \"import pathlib,sys;pathlib.Path('{args}3.txt').write_text(sys.executable)\"",
                ],
            },
            **project.config.envs["default"],
        },
    )

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("-v", "run", "write-exe", "test")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        ─────────────────────────────────── default ────────────────────────────────────
        Creating environment: default
        Checking dependencies
        cmd [1] | python -c "import pathlib,sys;pathlib.Path('test1.txt').write_text(sys.executable)"
        cmd [2] | python -c "import pathlib,sys;pathlib.Path('test2.txt').write_text(sys.executable)"
        cmd [3] | python -c "import pathlib,sys;pathlib.Path('test3.txt').write_text(sys.executable)"
        """
    )
    output_files = []
    for i in range(1, 4):
        output_file = project_path / f"test{i}.txt"
        assert output_file.is_file()
        output_files.append(output_file)

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    for output_file in output_files:
        assert str(env_path) in str(output_file.read_text())


def test_matrix_no_environments(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(project, "test", {"matrix": []})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run", "test:python", "-c", "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])"
        )

    assert result.exit_code == 1, result.output
    assert result.output == helpers.dedent(
        """
        No variables defined for matrix: test
        """
    )


def test_matrix(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(project, "test", {"matrix": [{"version": ["9000", "42"]}]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run", "test:python", "-c", "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])"
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        ────────────────────────────────── test.9000 ───────────────────────────────────
        Creating environment: test.9000
        Checking dependencies
        ─────────────────────────────────── test.42 ────────────────────────────────────
        Creating environment: test.42
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = sorted(storage_path.iterdir(), key=lambda d: d.name)[::-1]
    assert len(env_dirs) == 2

    python_path1, python_path2 = str(output_file.read_text()).splitlines()
    for python_path, env_dir, env_name in zip(
        (python_path1, python_path2), env_dirs, ("test.9000", "test.42"), strict=False
    ):
        assert env_dir.name == env_name
        assert str(env_dir) in python_path


def test_incompatible_single(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(
        project, "default", {"skip-install": True, "platforms": ["foo"], **project.config.envs["default"]}
    )
    helpers.update_project_environment(project, "test", {})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run", "test:python", "-c", "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])"
        )

    assert result.exit_code == 1
    assert result.output == helpers.dedent(
        """
        Environment `test` is incompatible: unsupported platform
        """
    )
    output_file = project_path / "test.txt"
    assert not output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert not env_data_path.is_dir()


def test_incompatible_matrix_full(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(
        project, "default", {"skip-install": True, "platforms": ["foo"], **project.config.envs["default"]}
    )
    helpers.update_project_environment(project, "test", {"matrix": [{"version": ["9000", "42"]}]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run", "test:python", "-c", "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])"
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Skipped 2 incompatible environments:
        test.9000 -> unsupported platform
        test.42 -> unsupported platform
        """
    )
    output_file = project_path / "test.txt"
    assert not output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert not env_data_path.is_dir()


def test_incompatible_matrix_partial(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(
        project,
        "test",
        {
            "matrix": [{"version": ["9000", "42"]}],
            "overrides": {"matrix": {"version": {"platforms": [{"value": "foo", "if": ["9000"]}]}}},
        },
    )

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run", "test:python", "-c", "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])"
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        ─────────────────────────────────── test.42 ────────────────────────────────────
        Creating environment: test.42
        Checking dependencies

        Skipped 1 incompatible environment:
        test.9000 -> unsupported platform
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]
    assert env_path.name == "test.42"

    python_path = str(output_file.read_text()).strip()
    assert str(env_path) in python_path


def test_incompatible_missing_python(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    known_version = "".join(map(str, sys.version_info[:2]))
    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(project, "test", {"matrix": [{"python": [known_version, "9000"]}]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run", "test:python", "-c", "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])"
        )

    padding = "─"
    if len(known_version) < 3:
        padding += "─"

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        f"""
        ────────────────────────────────── test.py{known_version} ─────────────────────────────────{padding}
        Creating environment: test.py{known_version}
        Checking dependencies

        Skipped 1 incompatible environment:
        test.py9000 -> cannot locate Python: 9000
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]
    assert env_path.name == f"test.py{known_version}"

    python_path = str(output_file.read_text()).strip()
    assert str(env_path) in python_path


def test_env_detection(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(project, "foo", {})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("env", "create")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Creating environment: default
        Checking dependencies
        """
    )

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("env", "create", "foo")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Creating environment: foo
        Checking dependencies
        """
    )

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = sorted(storage_path.iterdir(), key=lambda d: d.name)
    assert len(env_dirs) == 2

    assert env_dirs[0].name == "foo"
    assert env_dirs[1].name == "my-app"

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path), AppEnvVars.ENV_ACTIVE: "foo"}):
        result = hatch("run", "python", "-c", "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)")

    assert result.exit_code == 0, result.output

    output_file = project_path / "test.txt"
    assert output_file.is_file()

    python_path = str(output_file.read_text()).strip()
    assert str(env_dirs[0]) in python_path


def test_env_detection_override(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(project, "foo", {})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("env", "create")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Creating environment: default
        Checking dependencies
        """
    )

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("env", "create", "foo")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Creating environment: foo
        Checking dependencies
        """
    )

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = sorted(storage_path.iterdir(), key=lambda d: d.name)
    assert len(env_dirs) == 2

    assert env_dirs[0].name == "foo"
    assert env_dirs[1].name == "my-app"

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path), AppEnvVars.ENV_ACTIVE: "foo"}):
        result = hatch(
            "run", "default:python", "-c", "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)"
        )

    assert result.exit_code == 0, result.output

    output_file = project_path / "test.txt"
    assert output_file.is_file()

    python_path = str(output_file.read_text()).strip()
    assert str(env_dirs[1]) in python_path


def test_matrix_variable_selection_no_command(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(project, "test", {"matrix": [{"version": ["9000", "42"]}]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("run", "+version=9000")

    assert result.exit_code == 1, result.output
    assert result.output == helpers.dedent(
        """
        Missing argument `MATRIX:ARGS...`
        """
    )


def test_matrix_variable_selection_duplicate_inclusion(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(project, "test", {"matrix": [{"version": ["9000", "42"]}]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "+version=9000",
            "+version=42",
            "python",
            "-c",
            "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)",
        )

    assert result.exit_code == 1, result.output
    assert result.output == helpers.dedent(
        """
        Duplicate included variable: version
        """
    )


def test_matrix_variable_selection_duplicate_exclusion(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(project, "test", {"matrix": [{"version": ["9000", "42"]}]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "-version=9000",
            "-version=42",
            "python",
            "-c",
            "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)",
        )

    assert result.exit_code == 1, result.output
    assert result.output == helpers.dedent(
        """
        Duplicate excluded variable: version
        """
    )


def test_matrix_variable_selection_python_alias(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(project, "test", {"matrix": [{"python": ["9000", "42"]}]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "+py=9000",
            "+python=42",
            "python",
            "-c",
            "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)",
        )

    assert result.exit_code == 1, result.output
    assert result.output == helpers.dedent(
        """
        Duplicate included variable: python
        """
    )


def test_matrix_variable_selection_not_matrix(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(project, "test", {"matrix": [{"version": ["9000", "42"]}]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "+version=9000",
            "python",
            "-c",
            "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])",
        )

    assert result.exit_code == 1, result.output
    assert result.output == helpers.dedent(
        """
        Variable selection is unsupported for non-matrix environments: default
        """
    )


def test_matrix_variable_selection_inclusion(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(project, "test", {"matrix": [{"version": ["9000", "42"]}]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "+version=9000",
            "test:python",
            "-c",
            "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])",
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        ────────────────────────────────── test.9000 ───────────────────────────────────
        Creating environment: test.9000
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]
    assert env_path.name == "test.9000"

    python_path = str(output_file.read_text()).strip()
    assert str(env_path) in python_path


def test_matrix_variable_selection_exclusion(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(project, "test", {"matrix": [{"version": ["9000", "42"]}]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "-version=9000",
            "test:python",
            "-c",
            "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])",
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        ─────────────────────────────────── test.42 ────────────────────────────────────
        Creating environment: test.42
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]
    assert env_path.name == "test.42"

    python_path = str(output_file.read_text()).strip()
    assert str(env_path) in python_path


def test_matrix_variable_selection_exclude_all(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(project, "test", {"matrix": [{"version": ["9000", "42"]}]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "-version",
            "test:python",
            "-c",
            "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])",
        )

    assert result.exit_code == 1, result.output
    assert result.output == helpers.dedent(
        """
        No environments were selected
        """
    )


def test_matrix_variable_selection_include_none(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(project, "test", {"matrix": [{"version": ["9000", "42"]}]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "+version=3.14",
            "test:python",
            "-c",
            "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])",
        )

    assert result.exit_code == 1, result.output
    assert result.output == helpers.dedent(
        """
        No environments were selected
        """
    )


def test_matrix_variable_selection_inclusion_multiple_variables(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})
    helpers.update_project_environment(
        project, "test", {"matrix": [{"version1": ["9000", "42"], "version2": ["3.14"]}]}
    )

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run",
            "+version1=9000",
            "test:python",
            "-c",
            "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])",
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        ──────────────────────────────── test.9000-3.14 ────────────────────────────────
        Creating environment: test.9000-3.14
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]
    assert env_path.name == "test.9000-3.14"

    python_path = str(output_file.read_text()).strip()
    assert str(env_path) in python_path


def test_context_formatting_recursion(hatch, helpers, temp_dir, config_file):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(
        project,
        "default",
        {
            "skip-install": True,
            "scripts": {"py": 'python -c "{env:FOO:{env:BAR:{env:BAZ}}}"'},
            **project.config.envs["default"],
        },
    )

    with project_path.as_cwd(
        env_vars={
            ConfigEnvVars.DATA: str(data_path),
            "BAZ": "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)",
        }
    ):
        result = hatch("run", "py")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Creating environment: default
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    assert str(env_path) in str(output_file.read_text())


def test_plugin_dependencies_unmet(hatch, helpers, temp_dir, config_file, mock_plugin_installation):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    dependency = os.urandom(16).hex()
    (project_path / DEFAULT_CONFIG_FILE).write_text(
        helpers.dedent(
            f"""
            [env]
            requires = ["{dependency}"]
            """
        )
    )

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch("run", "python", "-c", "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)")

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        """
        Syncing environment plugin requirements
        Creating environment: default
        Checking dependencies
        """
    )
    helpers.assert_plugin_installation(mock_plugin_installation, [dependency])

    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    assert str(env_path) in str(output_file.read_text())


@pytest.mark.requires_internet
def test_install_python_specific(hatch, helpers, temp_dir, config_file, mocker, available_python_version):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(
        project,
        "default",
        {"skip-install": True, "python": available_python_version, **project.config.envs["default"]},
    )

    mocker.patch("hatch.env.virtual.VirtualEnvironment._interpreter_is_compatible", return_value=False)
    manager = PythonManager(data_path / "env" / "virtual" / ".pythons")
    assert not manager.get_installed()

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run", "python", "-c", "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])"
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        f"""
        Creating environment: default
        Installing Python distribution: {available_python_version}
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    assert str(env_path) in str(output_file.read_text())

    assert list(manager.get_installed()) == [available_python_version]


@pytest.mark.requires_internet
def test_update_python_specific(hatch, helpers, temp_dir, config_file, mocker, available_python_version):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(
        project,
        "default",
        {"skip-install": True, "python": available_python_version, **project.config.envs["default"]},
    )

    install_dir = data_path / "env" / "virtual" / ".pythons"
    manager = PythonManager(install_dir)
    dist = manager.install(available_python_version)
    helpers.downgrade_distribution_metadata(install_dir / available_python_version)
    mocker.patch(
        "hatch.env.virtual.VirtualEnvironment._interpreter_is_compatible",
        side_effect=lambda interpreter: Path(interpreter.executable) == dist.python_path,
    )

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run", "python", "-c", "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])"
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        f"""
        Creating environment: default
        Updating Python distribution: {available_python_version}
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    assert str(env_path) in str(output_file.read_text())


@pytest.mark.requires_internet
def test_install_python_max_compatible(hatch, helpers, temp_dir, config_file, mocker, available_python_version):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})

    mocker.patch("hatch.env.virtual.VirtualEnvironment._interpreter_is_compatible", return_value=False)
    manager = PythonManager(data_path / "env" / "virtual" / ".pythons")
    assert not manager.get_installed()

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run", "python", "-c", "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])"
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        f"""
        Creating environment: default
        Installing Python distribution: {available_python_version}
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    assert str(env_path) in str(output_file.read_text())

    assert list(manager.get_installed()) == [available_python_version]


@pytest.mark.requires_internet
def test_update_python_max_compatible(hatch, helpers, temp_dir, config_file, mocker, available_python_version):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    helpers.update_project_environment(project, "default", {"skip-install": True, **project.config.envs["default"]})

    install_dir = data_path / "env" / "virtual" / ".pythons"
    manager = PythonManager(install_dir)
    dist = manager.install(available_python_version)
    helpers.downgrade_distribution_metadata(install_dir / available_python_version)
    mocker.patch(
        "hatch.env.virtual.VirtualEnvironment._interpreter_is_compatible",
        side_effect=lambda interpreter: Path(interpreter.executable) == dist.python_path,
    )

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run", "python", "-c", "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])"
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        f"""
        Creating environment: default
        Updating Python distribution: {available_python_version}
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    assert str(env_path) in str(output_file.read_text())


@pytest.mark.requires_internet
def test_python_installation_with_metadata_hook(
    hatch, helpers, temp_dir, config_file, mocker, available_python_version
):
    config_file.model.template.plugins["default"]["tests"] = False
    config_file.save()

    project_name = "My.App"

    with temp_dir.as_cwd():
        result = hatch("new", project_name)

    assert result.exit_code == 0, result.output

    project_path = temp_dir / "my-app"
    data_path = temp_dir / "data"
    data_path.mkdir()

    project = Project(project_path)
    config = dict(project.raw_config)
    config["build-system"]["requires"].append("foo")
    config["tool"]["hatch"]["metadata"] = {"hooks": {"custom": {"dependencies": ["binary"]}}}
    project.save_config(config)

    helpers.update_project_environment(
        project,
        "default",
        {"skip-install": True, "python": available_python_version, **project.config.envs["default"]},
    )

    build_script = project_path / DEFAULT_BUILD_SCRIPT
    build_script.write_text(
        helpers.dedent(
            """
            from hatchling.metadata.plugin.interface import MetadataHookInterface

            class CustomMetadataHook(MetadataHookInterface):
                def update(self, metadata):
                    import binary
            """
        )
    )

    mocker.patch("hatch.env.virtual.VirtualEnvironment._interpreter_is_compatible", return_value=False)
    manager = PythonManager(data_path / "env" / "virtual" / ".pythons")
    assert not manager.get_installed()

    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
        result = hatch(
            "run", "python", "-c", "import os,sys;open('test.txt', 'a').write(sys.executable+os.linesep[-1])"
        )

    assert result.exit_code == 0, result.output
    assert result.output == helpers.dedent(
        f"""
        Creating environment: default
        Installing Python distribution: {available_python_version}
        Checking dependencies
        """
    )
    output_file = project_path / "test.txt"
    assert output_file.is_file()

    env_data_path = data_path / "env" / "virtual"
    assert env_data_path.is_dir()

    project_data_path = env_data_path / project_path.name
    assert project_data_path.is_dir()

    storage_dirs = list(project_data_path.iterdir())
    assert len(storage_dirs) == 1

    storage_path = storage_dirs[0]
    assert len(storage_path.name) == 8

    env_dirs = list(storage_path.iterdir())
    assert len(env_dirs) == 1

    env_path = env_dirs[0]

    assert env_path.name == project_path.name

    assert str(env_path) in str(output_file.read_text())

    assert list(manager.get_installed()) == [available_python_version]


class TestScriptRunner:
    @pytest.mark.requires_internet
    def test_not_file(self, hatch, helpers, temp_dir):
        project_name = "My.App"

        with temp_dir.as_cwd():
            result = hatch("new", project_name)

        assert result.exit_code == 0, result.output

        project_path = temp_dir / "my-app"
        data_path = temp_dir / "data"
        data_path.mkdir()

        project = Project(project_path)
        helpers.update_project_environment(
            project,
            "default",
            {"skip-install": True, "scripts": {"script.py": "python -c {args}"}, **project.config.envs["default"]},
        )

        with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
            result = hatch("run", "script.py", "import pathlib,sys;pathlib.Path('test.txt').write_text(sys.executable)")

        assert result.exit_code == 0, result.output
        assert result.output == helpers.dedent(
            """
            Creating environment: default
            Checking dependencies
            """
        )
        output_file = project_path / "test.txt"
        assert output_file.is_file()

        env_data_path = data_path / "env" / "virtual"
        assert env_data_path.is_dir()

        project_data_path = env_data_path / project_path.name
        assert project_data_path.is_dir()

        storage_dirs = list(project_data_path.iterdir())
        assert len(storage_dirs) == 1

        storage_path = storage_dirs[0]
        assert len(storage_path.name) == 8

        env_dirs = list(storage_path.iterdir())
        assert len(env_dirs) == 1

        env_path = env_dirs[0]

        assert env_path.name == project_path.name

        assert str(env_path) in str(output_file.read_text())

    @pytest.mark.requires_internet
    def test_dependencies(self, hatch, helpers, temp_dir):
        data_path = temp_dir / "data"
        data_path.mkdir()
        script = (temp_dir / "script.py").resolve()
        script.write_text(
            helpers.dedent(
                """
                # /// script
                # dependencies = [
                #   "binary",
                # ]
                # ///
                import pathlib
                import sys

                import binary

                pathlib.Path('test.txt').write_text(
                    f'{sys.executable}\\n{str(binary.convert_units(1024))}'
                )
                """
            )
        )

        with temp_dir.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
            result = hatch("run", "script.py")

        assert result.exit_code == 0, result.output
        assert result.output == helpers.dedent(
            f"""
            Creating environment: {script.id}
            Checking dependencies
            Syncing dependencies
            """
        )
        output_file = temp_dir / "test.txt"
        assert output_file.is_file()

        env_data_path = data_path / "env" / "virtual" / ".scripts"
        assert env_data_path.is_dir()

        env_path = env_data_path / script.id
        assert env_path.is_dir()
        assert env_path.name == script.id

        executable_path, unit_conversion = output_file.read_text().splitlines()
        executable = Path(executable_path)

        assert executable.is_file()
        assert data_path in executable.parents
        assert unit_conversion == "(1.0, 'KiB')"

    @pytest.mark.requires_internet
    def test_dependencies_from_tool_config(self, hatch, helpers, temp_dir):
        data_path = temp_dir / "data"
        data_path.mkdir()
        script = (temp_dir / "script.py").resolve()
        script.write_text(
            helpers.dedent(
                """
                # /// script
                # dependencies = []
                #
                # [tool.hatch]
                # dependencies = [
                #   "binary",
                # ]
                # ///
                import pathlib
                import sys

                import binary

                pathlib.Path('test.txt').write_text(
                    f'{sys.executable}\\n{str(binary.convert_units(1024))}'
                )
                """
            )
        )

        with temp_dir.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
            result = hatch("run", "script.py")

        assert result.exit_code == 0, result.output
        assert result.output == helpers.dedent(
            f"""
            Creating environment: {script.id}
            Checking dependencies
            Syncing dependencies
            """
        )
        output_file = temp_dir / "test.txt"
        assert output_file.is_file()

        env_data_path = data_path / "env" / "virtual" / ".scripts"
        assert env_data_path.is_dir()

        env_path = env_data_path / script.id
        assert env_path.is_dir()
        assert env_path.name == script.id

        executable_path, unit_conversion = output_file.read_text().splitlines()
        executable = Path(executable_path)

        assert executable.is_file()
        assert data_path in executable.parents
        assert unit_conversion == "(1.0, 'KiB')"

    def test_unsupported_python_version(self, hatch, helpers, temp_dir):
        data_path = temp_dir / "data"
        data_path.mkdir()
        script = (temp_dir / "script.py").resolve()
        script.write_text(
            helpers.dedent(
                """
                # /// script
                # requires-python = ">9000"
                # ///
                import pathlib
                import sys

                pathlib.Path('test.txt').write_text(sys.executable)
                """
            )
        )

        with temp_dir.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
            result = hatch("run", "script.py")

        assert result.exit_code == 1, result.output
        assert result.output == helpers.dedent(
            """
            Unable to satisfy Python version constraint: >9000
            """
        )

    @pytest.mark.requires_internet
    def test_python_version_constraint(self, hatch, helpers, temp_dir):
        data_path = temp_dir / "data"
        data_path.mkdir()
        script = (temp_dir / "script.py").resolve()

        # Cap the range at the current minor version so that the current Python
        # will be used and distributions don't have to be downloaded
        major, minor = sys.version_info[:2]
        minor += 1

        script.write_text(
            helpers.dedent(
                f"""
                # /// script
                # requires-python = "<{major}.{minor}"
                # ///
                import pathlib
                import sys

                pathlib.Path('test.txt').write_text(sys.executable)
                """
            )
        )

        with temp_dir.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
            result = hatch("run", "script.py")

        assert result.exit_code == 0, result.output
        assert result.output == helpers.dedent(
            f"""
            Creating environment: {script.id}
            Checking dependencies
            """
        )
        output_file = temp_dir / "test.txt"
        assert output_file.is_file()

        env_data_path = data_path / "env" / "virtual" / ".scripts"
        assert env_data_path.is_dir()

        env_path = env_data_path / script.id
        assert env_path.is_dir()
        assert env_path.name == script.id

        executable = Path(output_file.read_text())
        assert executable.is_file()
        assert data_path in executable.parents

    def test_python_version_constraint_from_tool_config(self, hatch, helpers, temp_dir):
        data_path = temp_dir / "data"
        data_path.mkdir()
        script = (temp_dir / "script.py").resolve()

        # Use the current minor version so that the current Python
        # will be used and distributions don't have to be downloaded
        major, minor = sys.version_info[:2]

        script.write_text(
            helpers.dedent(
                f"""
                # /// script
                # requires-python = ">9000"
                #
                # [tool.hatch]
                # python = "{major}.{minor}"
                # ///
                import pathlib
                import sys

                pathlib.Path('test.txt').write_text(sys.executable)
                """
            )
        )

        with temp_dir.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
            result = hatch("run", "script.py")

        assert result.exit_code == 0, result.output
        assert result.output == helpers.dedent(
            f"""
            Creating environment: {script.id}
            Checking dependencies
            """
        )
        output_file = temp_dir / "test.txt"
        assert output_file.is_file()

        env_data_path = data_path / "env" / "virtual" / ".scripts"
        assert env_data_path.is_dir()

        env_path = env_data_path / script.id
        assert env_path.is_dir()
        assert env_path.name == script.id

        executable = Path(output_file.read_text())
        assert executable.is_file()
        assert data_path in executable.parents
